보이지 않는 요소의 offsetWidth 값 문제
✒️ 2025-05-08 21:10 내용 수정
문제 상황
- 유투브 클론코딩 팀 프로젝트에서 비디오의 메뉴 항목을 만들고 비디오 카드 별로 메뉴의 위치를 지정하여 표시하는 작업을 진행했다.
- 사진처럼 특정 비디오 카드 요소 내의 버튼을 누르면 그 위치에 메뉴가 뜬다.
- 오른쪽 끝에 위치한 비디오 카드의 경우, 메뉴가 viewport보다 오른쪽에 생성되면 메뉴를 viewport 안쪽으로 위치 시키고,
overflow-x: hidden을 적용하여 x축 스크롤이 생기지 않도록 만들었다. - 그러나 맨 오른쪽에 위치한 비디오 카드의 버튼을 페이지 로딩 후 처음 누르면 메뉴가 조금 의도한 방향보다 엇나간 위치에 표시되었다.
- 오른쪽 끝에 있는 비디오 카드의 메뉴 버튼을 다시 누르면 정상적인 위치에 표시되었다.
- HTML의 대략적인 구조는 아래와 같다.
<!-- html -->
<div id="contents">
<div class="content">
<div class="thumbnail">
<!-- 썸네일 -->
</div>
<div class="details">
<div class="meta">
<!-- 비디오 내용 -->
</div>
<div>
<button class="menu-toggle-btn">버튼</button>
</div>
</div>
</div>
<div class="menu-list"></div>
</div>
- 비디오 메뉴의 css는 아래처럼 설정되어 있고, JavaScript로 비디오 카드 내의 다양한 요소들의 위치 값을 가져와 메뉴를 표시할 위치를 지정했다.
/* css */
.menu-list {
visibility: hidden;
display: block;
}
.menu-list.active {
visibility: visible;
}
// javascript
function show_menu(card_video_id, video_menu_div) {
// 비디오 카드 위치 정보 가져오기
const card = document.querySelector(`.content[data-video-id="${card_video_id}"]`);
const toggle_btn = card.querySelector(".menu-toggle-btn");
const button_rect = toggle_btn.getBoundingClientRect();
const contents_div = document.querySelector("#contents").getBoundingClientRect();
const meta_box = card.querySelector(".meta").getBoundingClientRect();
// contents의 문서 내 왼쪽 좌표
const contents_div_left = contents_div.left;
// 메뉴바 크기
const menu_width = video_menu_div.offsetWidth;
const menu_height = video_menu_div.offsetHeight;
// 기준 위치 (스크롤 포함 절대좌표)
let top = button_rect.top + window.scrollY - (58 + 12);
let left = button_rect.left - contents_div_left;
// 뷰포트 크기
const viewport_width = window.innerWidth;
const viewport_height = window.innerHeight;
// 메뉴가 화면 오른쪽을 넘으면 왼쪽으로 보정
if (left + menu_width + contents_div_left >= viewport_width) {
left = (viewport_width - menu_width - 24 - contents_div_left);
}
// 메뉴가 화면 하단을 넘으면 위쪽으로 띄움
if (top + menu_height >= viewport_height + window.scrollY) {
top -= (menu_height + 60);
}
video_menu_div.style.position = 'absolute';
// 버튼 하단에 현재 스크롤 위치까지 고려한 좌표로 메뉴 리스트 위치 설정
video_menu_div.style.top = `${top}px`;
video_menu_div.style.left = `${left}px`;
video_menu_div.classList.add("active");
}
원인 분석 및 해결 방법
원인 분석
- ChatGPT로 이 부분에 관한 질문을 여러 개 돌리면서 이것저것 시도한 결과 처음에 DOM에 비디오 메뉴가 추가된 직후에 브라우저가 렌더링을 완료하지 못해 크기 정보가
0이거나 의도한 값이 아닌 경우가 있다고 한다. - 이는 비디오 메뉴가 화면에 보이도록 렌더링 된 상태가 아니었기 때문에(
visibility: hidden)offsetWidth나offsetHeight가 잘못된 값이 나올 수 있다고 한다.
해결 방법
- ChatGPT가 제안한 방법은 2가지로, 첫 번째는
requestAnimationFrame()을 사용하는 것이었다. - 다만 현재 프로젝트의 호출 구조에서 이 방법을 적용하기 애매해 두 번째 방법으로 시도하였고, 문제를 해결했다.
// 메뉴를 DOM에 추가
contents.appendChild(video_menu_div);
// 다음 이벤트 루프(렌더 후)에서 위치 설정
requestAnimationFrame(() => {
show_menu(card.dataset.videoId, video_menu_div);
video_menu_div.dataset.activeButton = button_video_id;
});
- 두 번째 방법은
position: absolute를 추가하여 브라우저가 메뉴를 제대로 인식할 수 있도록 하는 방법이다. - 이 방법으로 처음으로 비디오 메뉴를 띄울 때 맨 오른쪽의 비디오 카드 버튼을 누른 경우 메뉴가 이상한 위치에 뜨지 않고 의도한 위치에 떴다.
.menu-list {
visibility: hidden;
/* 보이지 않더라도 미리 설정*/
position: absolute;
display: block;
}
.menu-list.active {
visibility: visible;
}